/*!
\copyright  Copyright (c) 2019-2024 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief      Support for touchpad (press only) where driver is supplied by sensor subsystem
*/

#ifdef HAVE_SENSORSS

#include <panic.h>
#include <stdlib.h>
#include <logging.h>
#include <pl_timers/pl_timers.h>
#include "adk_log.h"
#include "touchpad_sensorss.h"
#include "touch.h"
#include "touch_config.h"
#include "proximity.h"
#include "proximity_config.h"
#include "app_sensor.h"


/* Make the type used for message IDs available in debug tools */
LOGGING_PRESERVE_MESSAGE_TYPE(touch_sensor_messages)
ASSERT_MESSAGE_GROUP_NOT_OVERFLOWED(TOUCH, TOUCH_SENSOR_MESSAGE_END)

LOGGING_PRESERVE_MESSAGE_TYPE(sensor_subsystem_messages)
LOGGING_PRESERVE_MESSAGE_TYPE(touch_module_internal_messages)

/*!< Task information for touch pad */
touchTaskData app_touch;

TouchFlags tf = {
    .touchFlag = FALSE,
    .lastEventTouch = FALSE,
    .suppressSlide = FALSE,
};

/*! \brief Cancel all timers in use; used when resetting or in error recovery */
static void touchSensorSs_CancelTimers(void)
{
    touchTaskData *touch = &app_touch;
    /* cancel the held timer if receiving any touch event*/
    MessageCancelAll(&touch->task, TOUCH_INTERNAL_HELD_CANCEL_TIMER);
    MessageCancelAll(&touch->task, TOUCH_INTERNAL_HELD_TIMER);
    MessageCancelAll(&touch->task, TOUCH_INTERNAL_CLICK_TIMER);
}

/*! \brief Send action to anyone registered

    Check for registrants first to save message allocation if not needed.
*/
static void touchSensorSs_SendActionMessage(touch_action_t action)
{
    DEBUG_LOG_INFO("touchSensorSs_SendActionMessage enum:touch_action_t:0x%04x", action);

    if (TaskList_Size(TaskList_GetFlexibleBaseTaskList(TouchSensor_GetActionClientTasks())))
    {
        MESSAGE_MAKE(message, TOUCH_SENSOR_ACTION_T);

        message->action = action;
        TaskList_MessageSend(TaskList_GetFlexibleBaseTaskList(TouchSensor_GetActionClientTasks()),
                             TOUCH_SENSOR_ACTION, message);
    }
}

static bool touchSensorSs_MapAndSendEvents(touch_action_t action, bool send_raw_regardless)
{
    touchTaskData *touch = &app_touch;
    unsigned i;

    DEBUG_LOG_INFO("touchSensorSs_MapAndSendEvents enum:touch_action_t:%d send_raw_regardless:%d", action, send_raw_regardless);

    for (i=0; i < touch->action_table_size; i++)
    {
        if (action == touch->action_table[i].action)
        {
            MessageId id = touch->action_table[i].message;

            DEBUG_LOG_VERBOSE("touchSensorSs_MapAndSendEvents action enum:touch_action_t:%d message: 0x%x", action, id);

            touchSensorSs_SendActionMessage(action);
            TaskList_MessageSendId(TaskList_GetFlexibleBaseTaskList(TouchSensor_GetUiClientTasks()), id);
            return TRUE;
        }
    }

    if (send_raw_regardless)
    {
        touchSensorSs_SendActionMessage(action);
    }
    return FALSE;
}

static void touchSensorSs_SendTouchEvent(touch_action_t action)
{
    touchTaskData *touch = &app_touch;
    DEBUG_LOG("touchSensorSs_SendTouchEvent enum:touch_action_t:%d", action);

    touchSensorSs_CancelTimers();

    touchSensorSs_MapAndSendEvents(action, TRUE);

    if(action == HAND_COVER)
    {
        MessageSendLater(&touch->task, TOUCH_INTERNAL_HELD_CANCEL_TIMER, NULL, touchConfigPressCancelMs());
        tf.touchFlag = TRUE;
    }
    else
    {
        if(tf.lastEventTouch)
        {
            MessageSend(&touch->task, TOUCH_INTERNAL_HELD_RELEASE, NULL);
        }
        tf.touchFlag = FALSE;
    }
    tf.lastEventTouch = TRUE;
    tf.suppressSlide = FALSE;
}

static void touchSensorSs_HandleTouches(bool touched)
{
    DEBUG_LOG("touchSensorSs_HandleTouches");

    if (touched)
    {
        if (!tf.touchFlag)
        {
            touchSensorSs_SendTouchEvent(HAND_COVER);
        }
    }
    else
    {   if (tf.touchFlag)
        {
            touchSensorSs_SendTouchEvent(HAND_COVER_RELEASE);
        }
    }
}

static void touchSensorSs_HandleSwipe(uint8 swipe_event)
{
    touchTaskData *touch = &app_touch;
    DEBUG_LOG_INFO("IQS7222A  swipe_event 0x%x",swipe_event );
    /*! Only Handle Swipe Up and Down */
    if(tf.touchFlag && swipe_event)
    {
        touchSensorSs_CancelTimers();
        touch_action_t action;
        if(touch->number_of_press == 1)
        {
            action = TAP_SLIDE_UP;
        }
        else
        {
            action = SLIDE_UP;
        }
        if(swipe_event & 0x01)
        {
            if(touch->number_of_press == 1)
            {
                action = TAP_SLIDE_DOWN;
            }
            else
            {
                action = SLIDE_DOWN;
            }
        }
        touchSensorSs_MapAndSendEvents(action, TRUE);
      
        touch->number_of_press = 0;
        touch->number_of_seconds_held = 0;
        tf.lastEventTouch = FALSE;
        tf.suppressSlide = TRUE;
    }
        
}

static void touchSensorSs_ReadEvent(const SENSOR_SS_TOUCHPAD_EVENT_T *event)
{
    if (event->swipe_event)
    {
       if(!tf.suppressSlide)
            touchSensorSs_HandleSwipe(event->swipe_event);
    }
    touchSensorSs_HandleTouches(!!event->touch_data); 
    
}

static void touchSensorSs_Reset(void)
{
    touchTaskData *touch = &app_touch;

    DEBUG_LOG("touchSensorSs_Reset");

    touchSensorSs_CancelTimers();

    touch->number_of_press = 0;
    touch->number_of_seconds_held = 0;

    tf.touchFlag = FALSE;
}

static void touchSensorSs_MapTouchTimerToLogicalInput(MessageId id, uint8 press, uint8 data)
{
    touch_action_t touch_ui_input = MAX_ACTION;
    bool found = FALSE;

    DEBUG_LOG("touchSensorSs_MapTouchTimerToLogicalInput MSG:touch_module_internal_messages:%d press:%d data:%d",
                                id,press,data);

    /* convert timer count to touch event map*/
    switch(id)
    {
        case TOUCH_INTERNAL_HELD_TIMER:
            /* press hold handling */
            switch (press)
            {
                case SINGLE_PRESS:
                    touch_ui_input = data + TOUCH_PRESS_HOLD_OFFSET;
                    break;

                case DOUBLE_PRESS:
                    touch_ui_input = data + TOUCH_DOUBLE_PRESS_HOLD_OFFSET;
                    break;

                /* only handle up to double press hold for now */
                default:
                    return;
            }
            break;

        case TOUCH_INTERNAL_HELD_RELEASE:
            /* hold release handling */
            switch (press)
            {
                case SINGLE_PRESS:
                    touch_ui_input = (touch_action_t) data + TOUCH_PRESS_RELEASE_OFFSET;
                    break;

                case DOUBLE_PRESS:
                    touch_ui_input = (touch_action_t) data + TOUCH_DOUBLE_PRESS_HOLD_RELEASE_OFFSET;
                    break;

                /* hold release only handle up to double press for now */
                default:
                    return;
            }
            break;

        case TOUCH_INTERNAL_CLICK_TIMER:
            /* quick press handling */
            if (press >= MAX_PRESS_SUPPORT)
            {
                return;
            }
            /* cast up the type */
            touch_ui_input = (touch_action_t) press;
            break;
    }

    if (touch_ui_input != MAX_ACTION)
    {
        /* try to match input action with UI message to be broadcasted*/
        DEBUG_LOG_DEBUG("Send Event 0x%02x", touch_ui_input);
        found = touchSensorSs_MapAndSendEvents(touch_ui_input, TRUE);

        /* for release event, find the closest release UI event, if not found the exact release timer UI event  */
        if (id == TOUCH_INTERNAL_HELD_RELEASE)
        {
            while (touch_ui_input && touch_ui_input > TOUCH_PRESS_RELEASE_OFFSET && !found)
            {
                touch_ui_input --;

                /* This is a generated action. Only send the action if we send a UI event */
                found = touchSensorSs_MapAndSendEvents(touch_ui_input, FALSE);
            }
        }
    }
}

static void touchSensorSs_MessageHandler(Task task, MessageId id, Message msg)
{
    touchTaskData *touch = (touchTaskData *) task;
    
    DEBUG_LOG("touchSensorSs_MessageHandler MSG:touch_module_internal_messages:%d", id);

    switch(id)
    {
        case SENSOR_SS_TOUCH_EVENT:
            touchSensorSs_ReadEvent((const SENSOR_SS_TOUCHPAD_EVENT_T *)msg);
            break;

        case TOUCH_INTERNAL_HELD_CANCEL_TIMER:
            /* This time out is to prevent accidental very quick touch, if this timer expired then a touch is counted */
            touch->number_of_seconds_held = 0;
            MessageSendLater(&touch->task, TOUCH_INTERNAL_HELD_TIMER, NULL, D_SEC(1) - touchConfigPressCancelMs());
            break;

        case TOUCH_INTERNAL_HELD_TIMER:
            /* send notification if we have button held subscription before increasing the counter*/
            touch->number_of_seconds_held++;
            DEBUG_LOG_DEBUG("Touch %u held %u seconds", touch->number_of_press+1, touch->number_of_seconds_held);
            touchSensorSs_MapTouchTimerToLogicalInput(id, touch->number_of_press + 1, touch->number_of_seconds_held);

            /* to recover from a loss of release event */
            if (touch->number_of_seconds_held <= touchConfigMaximumHeldTimeSeconds())
            {
                MessageSendLater(&touch->task, TOUCH_INTERNAL_HELD_TIMER, NULL, D_SEC(1));
            }
            else
            {
                touch->number_of_seconds_held = 0;
                touchSensorSs_Reset();
            }
            break;

        case TOUCH_INTERNAL_HELD_RELEASE:
            /* send notification if we have held release subscription then reset the counter*/
            MessageSendLater(&touch->task, TOUCH_INTERNAL_CLICK_TIMER, NULL, touchConfigClickTimeoutlMs());
            if (touch->number_of_seconds_held > 0)
            {
                /* long press release */
                DEBUG_LOG_VERBOSE("Touch %u held release %u seconds", touch->number_of_press, touch->number_of_seconds_held);
                touchSensorSs_MapTouchTimerToLogicalInput(id, touch->number_of_press + 1, touch->number_of_seconds_held);
                touch->number_of_press = 0;
            }
            else
            {
                /* quick press release*/
                touch->number_of_press++;
                DEBUG_LOG_VERBOSE("Quick press %u", touch->number_of_press);
            }
            touch->number_of_seconds_held = 0;
            break;

        case TOUCH_INTERNAL_CLICK_TIMER:
            /* if this is expired, meaning the quick click hasn't been cancelled, can send multi click event*/
            if (touch->number_of_press)
            {
                DEBUG_LOG_VERBOSE("Quick press release %u", touch->number_of_press);
                touchSensorSs_MapTouchTimerToLogicalInput(TOUCH_INTERNAL_CLICK_TIMER, touch->number_of_press, 0);
                touch->number_of_press = 0;
            }
            break;

        default:
            break;
    }
}

static void touchSensorSs_StartIfNeeded(void)
{
    touchTaskData *touch = &app_touch;

    DEBUG_LOG("touchSensorSs_StartIfNeeded t->config=%d",touch->config);

    if (!touch->config)
    {
        touch->task.handler = touchSensorSs_MessageHandler;

        touch->config = (touchConfig*)1; /* Use like a bool */
        touchSensorSs_Reset();
    }
}

static void touchSensorSs_StopIfNeeded(void)
{
    touchTaskData *touch = &app_touch;

    DEBUG_LOG("touchSensorSs_StopIfNeeded t->config=%d",touch->config);

    if (touch->config)
    {
        uint16 ui_clients = TaskList_Size(TaskList_GetFlexibleBaseTaskList(TouchSensor_GetUiClientTasks()));
        uint16 action_clients = TaskList_Size(TaskList_GetFlexibleBaseTaskList(TouchSensor_GetActionClientTasks()));

        if (!ui_clients && !action_clients)
        {
            touch->config = NULL;

            if (!ui_clients)
            {
                touch->action_table = NULL;
                touch->action_table_size = 0;
            }
        }
    }
}

bool TouchSensor_Init(Task init_task)
{
    UNUSED(init_task);

    TaskList_InitialiseWithCapacity(TouchSensor_GetUiClientTasks(),
                                    TOUCH_CLIENTS_INITIAL_CAPACITY);
    TaskList_InitialiseWithCapacity(TouchSensor_GetActionClientTasks(),
                                    TOUCH_CLIENTS_INITIAL_CAPACITY);

    return TRUE;
}

bool TouchSensorClientRegister(Task task, uint32 size_action_table, const touch_event_config_t *action_table)
{
    touchTaskData *touch = &app_touch;
    bool ret = FALSE;

    ret =  TaskList_AddTask(TaskList_GetFlexibleBaseTaskList(TouchSensor_GetUiClientTasks()), task);

    if(!ret)
        return ret;

    /* update action table*/
    if (size_action_table && action_table != NULL)
    {
        touch->action_table = action_table;
        touch->action_table_size = size_action_table;
    }

    touchSensorSs_StartIfNeeded();

    return ret;
}

void TouchSensorClientUnRegister(Task task)
{
    TaskList_RemoveTask(TaskList_GetFlexibleBaseTaskList(TouchSensor_GetUiClientTasks()), task);

    touchSensorSs_StopIfNeeded();
}

bool TouchSensorActionClientRegister(Task task)
{
    bool ret = FALSE;

    ret = TaskList_AddTask(TaskList_GetFlexibleBaseTaskList(TouchSensor_GetActionClientTasks()), task);

    if(!ret)
        return ret;

    touchSensorSs_StartIfNeeded();

    return ret;
}

void TouchSensorActionClientUnRegister(Task task)
{
    TaskList_RemoveTask(TaskList_GetFlexibleBaseTaskList(TouchSensor_GetActionClientTasks()), task);

    touchSensorSs_StopIfNeeded();
}

/* if hold = TRUE, then reset the sensor but do not allow events to be generated */
bool TouchSensor_Reset(bool hold)
{
    touchTaskData *touch = &app_touch;

    DEBUG_LOG_INFO("TouchSensor_Reset(); config? 0x%p, disable events? %d", touch->config, hold);
    if (touch->config) 
    {
        touchSensorSs_Reset();
        return TRUE;
    }
    return FALSE;
}

Task TouchSensorGetTask(void)
{
    return &app_touch.task;
}

bool AppTouchSensorGetDormantConfigureKeyValue(dormant_config_key *key, uint32* value)
{
    return FALSE;
}

#endif /* HAVE_SENSORSS */

